/*
 * @Author: czy
 * @Date: 2021-04-04 22:39:40
 * @LastEditTime: 2021-04-10 21:42:37
 */
extern void outb(unsigned short int port_to, unsigned char value);
extern unsigned char inb(unsigned short int port_from);

// VGA properties.
#define vga_base 0xB8000
#define ROW 25
#define COL 80

// Cursor properties
#define index_port 0x3D4
#define data_port 0x3D5
#define row_port 0xE
#define col_port 0xF

// Control character properties
#define NEWLINE COL
#define TAB 8

typedef signed short int int16;
static struct
{
    int16 *head;    // Head pointer.
    int16 *tail;    // Tail pointer.
    int16 *cursor;  // Cursor pointer.
    int rows;       // Number of rows.
    int columns;    // Number of columns.
} vga = {(int16 *)vga_base, (int16 *)vga_base + ROW *COL, (int16 *)vga_base, ROW, COL};

// Set cursor
static void reset_cursor(void)
{
    int num = vga.cursor - vga.head;
    int col_num, row_num;
    col_num = num % 256;
    row_num = num / 256;
    outb(index_port, col_port);
    outb(data_port, col_num);
    outb(index_port, row_port);
    outb(data_port, row_num);
}

// Clear the screen and return the cursor to its original value.
void clear_screen(void)
{
    for (int16 *p = vga.head; p < vga.tail; p++)
        *p = (int16)0x0700;
    vga.cursor = vga.head;
    reset_cursor();
}

// The screen scrolls down one line.
static void scrolling(void)
{
    int16 *p = vga.head;
    int16 temp;
    for (; p < vga.tail - vga.columns; p++)
    {
        temp = *(p + vga.columns);
        *p = temp;
    }
    for (int i = 0; i < vga.columns; i++)
    {
        *p = (int16)0x0700;
        p++;
    }
    vga.cursor = vga.tail - vga.columns;
}

// Control character: \n
static void newline(void)
{
    vga.cursor = vga.head + (((vga.cursor - vga.head) / NEWLINE) + 1) * NEWLINE;
}

// Control character: \t
static void tab(void)
{
    vga.cursor = vga.head + (((vga.cursor - vga.head) / TAB) + 1) * TAB;
}

// Control character: \b
static void backspace(void)
{
    vga.cursor--;
}

// Control character: \r
static void carriage(void)
{
    vga.cursor = vga.head + ((vga.cursor - vga.head) / NEWLINE) * NEWLINE;
}

// Common characters
static void put_character(char ch, int color)
{
    *(vga.cursor) = (int16)(color << 8 | ch);
    vga.cursor++;
}

/**
 * @description: Output a string to VGA.
 * @param str   The first address of the string.
 * @param color Output color.
 */
void append2screen(char *str, int color)
{
    while (*str != '\0')
    {
        switch (*str++)
        {
        case '\n':
            newline();
            break;
        case '\t':
            tab();
            break;
        case '\b':
            backspace();
            break;
        case '\r':
            carriage();
            break;
        default:
            put_character(*(str - 1), color);
            break;
        }
        if (vga.cursor >= vga.tail)
            scrolling();
    }
    reset_cursor();
}